package com.hjx.jssupport;

import static com.hjx.system.NashornUtil.syncImportsToScriptContext;
import static java.util.Arrays.asList;

import com.hjx.coverage.CoverageHelper;
import com.hjx.system.NashornUtil;
import com.hjx.system.PartReadOnlyContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JSCoverageRunner extends Runner {

    private static Logger logger = LoggerFactory.getLogger(JSRunner.class);
    //所有测试类，一个js文件就是一个测试类
    private List<TestClass> tests;
    //标注Tests的注解类
    private final Class<?> cls;

    private ScriptEngine scriptEngine;
//    private SimpleScriptContext scriptContext;
    //业务上下文
    private Map context = new PartReadOnlyContext();

    private CoverageHelper coverageHelper;

    public JSCoverageRunner(Class<?> cls) throws ScriptException, NoSuchMethodException {
        this.cls = cls;
        //注解值（js文件路径数组）转为list
        List<String> testNames = asList(cls.getAnnotation(Tests.class).value());
        List testNameList = testNames.stream().distinct().map(jsPath -> "target/dest/" + jsPath).collect(Collectors.toList());
        scriptEngine = createScriptEngineManager();
        this.coverageHelper = new CoverageHelper();
        coverageHelper.prepareForCoverageReport();

        this.tests = findJSTests(testNameList);
    }



    private List<TestClass> findJSTests(List<String> testNames) {
        try {
//            scriptEngine.eval("load('target/dest/testjs/common/util.js')");
//            scriptEngine.eval("load('target/dest/testjs/common/imports.js')");
            List<TestClass> testClasses = new ArrayList<TestClass>();
            for (String name : testNames) {
                ScriptContext scriptContext = new SimpleScriptContext();
                syncImportsToScriptContext(scriptContext);
                context = new PartReadOnlyContext();

                scriptContext.setAttribute("context", context, ScriptContext.ENGINE_SCOPE);
                scriptContext.setAttribute("logger", logger, ScriptContext.ENGINE_SCOPE);

                context.put("scriptEngine", scriptEngine);
                context.put("scriptContext", scriptContext);

                scriptEngine.eval("var result = {};", scriptContext);
                scriptEngine.eval("load('target/dest/testjs/common/TestUtils.js')", scriptContext);
                context.put("is_test",true);
                testClasses.add(new TestClass(name, (List<TestCase>) scriptEngine.eval("load('" + name + "')", scriptContext), scriptContext));
            }
            return testClasses;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected ScriptEngine createScriptEngineManager() throws ScriptException, NoSuchMethodException {
        ScriptEngine scriptEngine = NashornUtil.getScriptEngine();
        return scriptEngine;
    }


    /**
     * 描述是给不同IDE插件看的，在不同IDE上有不同的可视化工具
     * @return
     */
    @Override
    public Description getDescription() {
        //测试套件描述
        Description suite = Description.createSuiteDescription(cls);
        for (TestClass testClass : tests) {
            List<TestCase> tests = testClass.testCases;
            //测试套件中JS文件描述
            Description desc = Description.createTestDescription(testClass.junitName(), testClass.junitName());
            suite.addChild(desc);
            for (TestCase test : tests) {
                //测试用例描述
                Description methodDesc = Description.createTestDescription(testClass.junitName(), test.name);
                desc.addChild(methodDesc);
            }
        }
        return suite;
    }

    /**
     * 通知IDE 测试用例运行状态
     * @param notifier
     */
    @Override
    public void run(RunNotifier notifier) {


        for (TestClass testClass : tests) {
            List<TestCase> tests = testClass.testCases;
            for (TestCase test : tests) {
                Description desc = Description.createTestDescription(testClass.junitName(), test.name);
                notifier.fireTestStarted(desc);
                try {
                    test.testCase.run();
                    notifier.fireTestFinished(desc);
                } catch (Exception | Error e) {
                    notifier.fireTestFailure(new Failure(desc, bestException(e)));
                }
            }
            this.coverageHelper.coverageToJSON(scriptEngine, testClass.scriptContext, testClass.coverageFileName());
        }

        this.coverageHelper.mergeJSON();




    }


    private Throwable bestException(Throwable e) {
        if (nashornException(e))
            return e.getCause() != null ? e.getCause() : e;
        return e;
    }

    private boolean nashornException(Throwable e) {
        return e.getClass().getSimpleName().contains("ECMA");
    }

}
